/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-04
 * V4.0
 */
package com.jphenix.one.boot;


import com.jphenix.standard.docs.ClassInfo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Properties;

/**
 * 通过配置文件启动指定应用
 * 
 * 注意：该类不需要其它类支持，需要单独拿出来
 * 打成jar包，否则加载应用时，再加载同样包含该类的jar包
 * 那个包就无效了，会导致启动时文件定位错误。
 * 
 * com.jphenix.one.boot.Startup
 * 
 * 配置文件为默认jar包同级文件夹中
 * 配置文件默认名称：phenix_startup.conf
 * 
 * 格式内容
 * 
 * 需要加载的jar包或类所在的文件夹路径
 * 序号从0开始，为连续的数字，中间不能断
 * class_path_0=
 * class_path_1=../lib
 * 
 * 调用启动类所需传入的参数
 * 需要从0开始，为连续数字，中间不能断
 * parameter_0=start
 * 
 * 启动类路径
 * boot_class=org.apache.catalina.startup.Bootstrap
 * 
 * @author 马宝刚
 * 2014年3月28日
 */
@ClassInfo({"2014-06-04 20:07","通过配置文件启动指定应用"})
public class Startup {

    //配置文件路径
    protected String configFilePath = null;
    
    //当前根路径
    protected String basePath = null;
    
    //添加路径方法对象
    protected Method addUrlMethod = null;
    
    /**
     * 构造函数
     * @author 马宝刚
     */
    public Startup() {
       super();
    }

    /**
     * 入口
     * @param args 传入参数
     * 2014年3月28日
     * @author 马宝刚
     */
    public static void main(String[] args) {
        //构建启动类 
        Startup startup = new Startup();
        try {
            startup.startup(args);
        }catch(Exception e) {
            e.printStackTrace();
            System.out.println("\n\n12-----------------------------------------------------------------------\nThe Application Has Bean Stoped\n--------------------------------------------------------------------------------------\n");
            System.exit(-1);
        }
    }

    
    /**
     * 执行启动
     * 第一个传入参数为 1  时，采用直传参数方式（非指定配置文件方式）加载参数
     * 
     * 直传参数方式：
     * 
     * 第一个参数： 1
     * 第二个参数： 启动类路径
     * 第三个参数：指定扫描加载类路径（多个类路径用分隔符分隔，windows采用分号，linux采用冒号）
     * 第3+n个参数：目标启动类所需要传入的参数
     * 
     * @param args 传入参数
     * @throws Exception 异常
     * 2014年3月28日
     * @author 马宝刚
     */
    public void startup(String[] args) throws Exception {
    	if(args!=null) {
    		if(args.length==1 && "?".equals(args[0])) {
    			//显示帮助信息
    			showHelp();
    			return;
    		}
    		if(args.length>2 && "1".equals(args[0])) {
    			
    			/*
    			 * 第一个传入参数为 1  时，采用直传参数方式（非指定配置文件方式）加载参数
    			 */
    			
    			
    			//加载直传参数
    			loadDef(args[1],args[2],args);
    			return;
    		}
    	}
        //当前包的路径
        String jarPath = getFilePath(getBaseClassPath(Startup.class));
        
        //配置文件路径
        if(args!=null && args.length>0 && args[0]!=null && args[0].length()>0) {
            configFilePath = getAllFilePath(args[0], jarPath);
        }else {
            configFilePath = getAllFilePath("phenix_startup.conf", jarPath);
        }
        if(!(new File(configFilePath)).exists()) {
            throw new Exception("Not Find The Config File For Startup  File Path:["+configFilePath+"]");
        }
        basePath = getFilePath(configFilePath);
        
        //tools.jar 路径
        String jdkToolsPath = getAllFilePath("../lib/tools.jar",System.getProperty("java.home"));
        if((new File(jdkToolsPath)).exists()) {
            addPath(jdkToolsPath);
        }
        
        //将当前文件夹放入类路径
        addPath("");
        
        //构建配置文件类
        Properties properties = new Properties();
        //执行加载
        properties.load(new FileInputStream(configFilePath));
        
        int index = 0; //参数索引
        String propValue; //参数值
        while(properties.containsKey("class_path_"+index)) {
            propValue = properties.getProperty("class_path_"+index);
            index++;
            if(propValue==null) {
                propValue = "";
            }
            addPath(propValue.trim());
        }
        
        //传入参数序列
        ArrayList<String> propList = new ArrayList<String>();
        index = 0; //参数索引
        while(properties.containsKey("parameter_"+index)) {
            propValue = properties.getProperty("parameter_"+index);
            index++;
            if(propValue==null) {
                propValue = "";
            }
            propList.add(propValue.trim());
        }
        //构造传入参数数组
        String[] propArrs = new String[propList.size()];
        propList.toArray(propArrs);
        //启动类路径
        String bootClass = properties.getProperty("boot_class");
        //执行启动
        executeBootClass(bootClass,propArrs);
    }
    
    
    /**
     * 加载直传参数
     * @param bootClass    启动类
     * @param classPath    指定类路径
     * @param args         启动参数（从启动参数中获取目标程序需要用到的启动参数）
     * @throws Exception   异常
     * 2017年1月31日
     * @author MBG
     */
    protected void loadDef(String bootClass,String classPath,String[] args) throws Exception {
        
        //tools.jar 路径
        String jdkToolsPath = getAllFilePath("../lib/tools.jar",System.getProperty("java.home"));
        if((new File(jdkToolsPath)).exists()) {
            addPath(jdkToolsPath);
        }
        
        //将当前文件夹放入类路径
        //原本启动程序的jar包是放在 tomcat的bin文件夹中的。
        //但是调用这个方法时，是直接引用项目中的包
        //所以下面这行语句就无效了
        //addPath(""); 
        
        //当前包的路径
        String jarPath = getFilePath(getBaseClassPath(Startup.class));
        addPath(jarPath); //加载项目中的jar包
        
        if(classPath!=null) {
        	classPath = classPath.trim();
        	//分隔成多个类路径
        	String[] paths = classPath.split(File.pathSeparator);
        	for(int i=0;i<paths.length;i++) {
        		paths[i] = paths[i].trim();
            	addPath(paths[i]); //按照全路径加载
            	
                //尝试转换成全路径加载
            	////没必要，肯定是全路径，启动配置文件中可以设置全路径的
                //addPath(getAllFilePath(paths[i],jarPath));
        	}
        }
        //传入参数序列
        ArrayList<String> propList = new ArrayList<String>();
        int index = 3; //参数索引
        while(index<args.length) {
        	propList.add(args[index++]);
        }
        //构造传入参数数组
        String[] propArrs = new String[propList.size()];
        propList.toArray(propArrs);
        //执行启动
        executeBootClass(bootClass,propArrs);
    }
    
    
    /**
     * 显示帮助信息
     * 2017年1月31日
     * @author MBG
     */
    protected void showHelp() {
    	System.out.println("Nobody ever saw this. See the source code Please.");
    }
    
    
    /**
     * 获取类的根路径
     * 
     * 如果这个FilesUtil类在Jar包中
     * 并且Servlet容器将这个Jar包复制到缓存文件夹中运行
     * 则无法根据自身定位到类的根路径
     * 
     * 所以需要指定一个在根路径中的类
     * 
     * @author 刘虻
     * 2009-7-17上午09:43:01
     * @param inCls 在根路径下的类
     * @return
     */
    protected String getBaseClassPath(Class<?> inCls) {
        if (inCls==null) {
            inCls = Startup.class;
        }
        //获取当前类URL
        URL url = 
            (inCls.getProtectionDomain()).getCodeSource().getLocation();
        String baseClassPath = url.getPath();
        //获取路径
        try {
            //从Class中获取到的路径都是UTF-8编码格式的
            baseClassPath = URLDecoder.decode(baseClassPath,"UTF-8");
        }catch(Exception e) {}
        return baseClassPath;
    }
    
    /**
     * 从文件全路径中获取文件路径
     * @author 刘虻
     * 2006-9-13下午02:54:15
     * @param filePathStr 文件全路径
     * @return 文件路径
     */
    protected String getFilePath(String filePathStr) {
        //导入参数合法化
        if (filePathStr == null 
                || filePathStr.length()==0) {
            return null;
        }
        //整理路径
        filePathStr = swapString(filePathStr,"\\","/");
        int lastPoint = filePathStr.lastIndexOf("/"); //最后的位置
        if (lastPoint>0) {
            return filePathStr.substring(0,lastPoint+1);
        }
        return "/";
    }
    
    /**
     * 替换字符串中指定的字符
     * @author 刘虻
     * @param str 要操作的字符串
     * @param fromStr 需要替换的字符串
     * @param toStr 替换成的字符串
     * @return 替换后的字符串
     * 2006-1-5  10:10:04
     */
    protected String swapString(String str,String fromStr,String toStr) {
        
        //导入参数合法化
        if (str==null) {
            return null;
        }
        String[] strs = split(str,fromStr);
        StringBuffer reStr = new StringBuffer();
        if (strs!=null && strs.length>0) {
            for (int i=0;i<strs.length;i++) {
                reStr.append(strs[i]); 
                if (i<strs.length-1) {
                    reStr.append(toStr); 
                }
            }
        }
        return reStr.toString();
    }
    
    
    /**
     * 将字符串按照指定字符分割为字符串数组
     * @author 刘虻
     * 2006-10-26下午11:54:09
     * @param source 原字符串
     * @param splitStr 指定分割字符
     * @return 分割后的字符串数组
     */
    protected String[] split(String source,String splitStr) {
        //获取分割后的序列
        ArrayList<String> reArrl = splitToList(source,splitStr);
        //构件返回值
        String[] reStrs = new String[reArrl.size()];
        //复制值
        reArrl.toArray(reStrs);
        return reStrs;
    }
    
    /**
     * 从字符串序列转换为序列
     * @author 刘虻
     * 2007-5-30下午08:31:55
     * @param source 源字符串
     * @param point 分割符
     * @return 转换后的序列
     */
    protected ArrayList<String> splitToList(String source,String point) {
        
        if (source==null || source.length()==0) {
            return new ArrayList<String>();
        }
        //构造返回容器
        ArrayList<String> reArrl = new ArrayList<String>();
        while(true) {
            //获取分割点
            int splitPoint = source.indexOf(point);
            if (splitPoint<0) {
                reArrl.add(source);
                break;
            }
            reArrl.add(source.substring(0,splitPoint));
            //构造分割后的字符串
            source = 
                source.substring(splitPoint+point.length());
        }
        return reArrl;
    }
    
    
    /**
     * 按照指定字符串数组将源字符串分割为序列
     * @author 刘虻
     * 2009-8-20下午03:34:25
     * @param source 源字符串
     * @param points 分割字符串数组
     * @return 分割后的序列
     */
    protected ArrayList<String> splitToList(
                    String source,String[] points) {
        if (source==null || source.length()==0 
                || points==null || points.length<1) {
            return new ArrayList<String>();
        }
        boolean isFirst = true; //是否首次截取
        //构造返回容器
        ArrayList<String> reArrl = new ArrayList<String>();
        //构建分割点
        int[] indexs = new int[points.length];
        for(int i=0;i<indexs.length;i++) {
            indexs[i] = source.indexOf(points[i]);
        }
        while(true) {
            //获取最小值
            int index = getMinIndex(indexs,-1);
            if(index<0) {
                if(isFirst) {
                    reArrl.add(source);
                }
                break;
            }
            isFirst = false;
            reArrl.add(source.substring(0,indexs[index]));
            //构造分割后的字符串
            source = 
                source.substring(indexs[index]+points[index].length());
            for(int i=0;i<indexs.length;i++) {
                indexs[i] = source.indexOf(points[i]);
            }
        }
        return reArrl;
    }
    
    
    /**
     * 获取整型数组中最小值的数组索引
     * @author 刘虻
     * 2009-8-20下午03:31:00
     * @param src 整型值数组
     * @param min 最小值
     * @return 最小值索引
     */
    protected int getMinIndex(int[] src,int min) {
        int thisMin = min; //最小值
        int reInt = -1; //最小值索引
        for(int i=0;i<src.length;i++) {
            if(min<src[i] && (thisMin>src[i] || thisMin==min)) {
                thisMin = src[i];
                reInt = i;
            }
        }
        return reInt;
    }
    
    
    /**
     * 执行启动类
     * @author 刘虻
     * 2009-8-21下午01:46:37
     * @param bootClassPath 启动类路径
     * @param args 传入参数
     * @throws Exception 执行发生异常
     */
    public void executeBootClass(String bootClassPath,String[] args) throws Exception {
        System.out.print("Begin Execute Class:["+bootClassPath+"] \n Params: [");
        if(args==null) {
            System.out.println("no param");
        }else {
            for(int i=0;i<args.length;i++) {
                System.out.print(args[i]+" ");
            }
        }
        System.out.println("]\n");
        //构建启动类
        Class<?> cls = Class.forName(bootClassPath);
        //获取启动方法
        Method main = cls.getMethod("main", String[].class);
        //执行启动
        main.invoke(null,new Object[] {args});
    }
    
    
    
    /**
     * 获取完整路径
     * @author 刘虻
     * 2007-6-18下午04:21:49
     * @param dummyPath 相对路径
     * @param basePath 类根路径
     * @return 完整路径
     */
    protected String getAllFilePath(String dummyPath,String basePath) {
        if(basePath==null) {
            basePath = "";
        }
        basePath = swapString(basePath,"\\","/");
        if (dummyPath==null || dummyPath.length()<1) {
            return basePath;
        }
        //转换分割符
        dummyPath = swapString(dummyPath,"\\","/");
        if(dummyPath.startsWith("//")) {
            dummyPath = "/"+swapString(dummyPath,"//","/");
        }else {
            dummyPath = swapString(dummyPath,"//","/");
        }
        //是否为根路经
        boolean isBase = false;
        if (dummyPath.startsWith("//")
                || dummyPath.startsWith("zip:")
                || dummyPath.startsWith("file:")
                || dummyPath.indexOf(":/")==1
                || dummyPath.indexOf(":/")==2
                || getFristPath(dummyPath).equals(getFristPath(basePath))) {
            isBase = true;
        }
        if (!isBase) {
            if (!dummyPath.startsWith("/")) {
                dummyPath = "/"+dummyPath;
            }
            if(basePath.endsWith("/")) {
                basePath = basePath.substring(0,basePath.length()-1);
            }
            dummyPath = basePath+dummyPath;
        }
        dummyPath = fixUpPath(dummyPath);
        if(!(new File(dummyPath)).exists()) {
            //搜索资源获取绝对路径
            URL pathUrl = null;
            if(dummyPath.startsWith("/")) {
                pathUrl = 
                    Startup.class
                        .getClassLoader()
                            .getResource(
                                    dummyPath.substring(1));
            }else {
                pathUrl = 
                        Startup.class
                        .getClassLoader()
                            .getResource(dummyPath);
            }
            if(pathUrl==null) {
                return dummyPath;
            }
            return getURLDecoding(pathUrl.getPath());
        }
        return dummyPath;
    }
    
    
    /**
     * 获取URL解码字符串
     * @author 刘虻
     * 2009-1-12上午11:20:03
     * @param urlStr url字符串
     * @return 解码字符串
     */
    protected String getURLDecoding(String urlStr) {
        return getURLDecoding(urlStr,null);
    }
    
    /**
     * @deprecated
     * 获取URL解码字符串
     * @author 刘虻
     * 2009-1-12上午11:18:58
     * @param urlStr url字符串
     * @param enc 字符串编码
     * @return 解码字符串
     */
    protected String getURLDecoding(String urlStr,String enc) {
        if (urlStr==null) {
            return "";
        }
        if (enc==null) {
            return URLDecoder.decode(urlStr);
        }
        try {
            return URLDecoder.decode(urlStr,enc);
        }catch(Exception e) {
            return urlStr;
        }
    }
    
    
    /**
     * 处理路径中的通配符
     * @author 刘虻
     * 2009-11-10下午03:09:56
     * @param dummyPath 待处理路径
     * @return 处理后的路径
     */
    protected String fixUpPath(String dummyPath) {
        StringBuffer rePathSbf = new StringBuffer(); //构造返回路径
        if (dummyPath.startsWith("/")) {
            rePathSbf.append("/");
            dummyPath = dummyPath.substring(1);
        }else if (dummyPath.indexOf(":/")==1) {
            rePathSbf.append(dummyPath, 0, 3);
            dummyPath = dummyPath.substring(3);
        }else if (dummyPath.indexOf(":/")==2) {
            rePathSbf.append(dummyPath, 0, 4);
            dummyPath = dummyPath.substring(4);
        }
        //分割成文件夹名
        ArrayList<String> pathSubArrayList = splitToList(dummyPath,"/");
        //整理后的文件夹序列
        ArrayList<String> fixArrayList = new ArrayList<String>();
        boolean isWar = false; //是否为war
        if (pathSubArrayList!=null) {
            for (int i=0;i<pathSubArrayList.size();i++) {
                
                //当前文件夹名
                String thisPath = String.valueOf(pathSubArrayList.get(i));
                //endsWith("!") 是因为如果部署成war格式 war文件名与展开时的根路径名不同
                if ("..".equals(thisPath)) {
                    if (fixArrayList.size()>0 ) {
                        if (String.valueOf(fixArrayList.get(fixArrayList.size()-1)).endsWith("!")) {
                            isWar = true;
                        }else {
                            fixArrayList.remove(fixArrayList.size()-1);
                        }
                    }
                }else {
                    if (isWar) {
                        isWar = false;
                    }else {
                        fixArrayList.add(thisPath);
                    }
                }
            }
        }
        for (int i=0;i<fixArrayList.size();i++) {
            if (i>0) {
                rePathSbf.append("/");
            }
            rePathSbf.append(fixArrayList.get(i));
        }
        return rePathSbf.toString();
    }
    
    /**
     * 获取第一个文件夹
     * @author 刘虻
     * 2008-1-30下午08:31:01
     * @param path 全路径
     * @return 第一个文件夹
     */
    protected String getFristPath(String path) {
        while(path.startsWith("/")) {
            path = path.substring(1);
        }
        //文件夹分割点
        int point = path.indexOf("/");
        if (point>-1) {
            return path.substring(0,point);
        }
        return "";
    }
    
    /**
     * 执行添加路径
     * @author 刘虻
     * 2009-8-21下午01:34:58
     * @param path 待添加的路径
     * @throws Exception 执行发生异常
     */
    public void addPath(String path) throws Exception {
        //搜索到的文件序列
        ArrayList<String> fileList = new ArrayList<String>();
        path = getAllFilePath(path,basePath);
        fileList.add(path);
        
        //扩展名序列
        ArrayList<String> extNameList = new ArrayList<String>();
        extNameList.add("jar");
        extNameList.add("zip");
        
        //执行搜索
        doSetFilePath(fileList,path,extNameList,true);
        for(String filePath:fileList) {
            if(filePath==null || filePath.length()<1) {
                continue;
            }
            System.out.println("Load Path:["+filePath+"]");
            addFilePath(filePath);
        }
    }
    
    
    /**
     * 获得指定路径下，符合文件类型的所有文件路径
     * @author 刘虻
     * @param filePathArrl 存放搜索后的文件路径
     * @param basePathStr 指定根文件夹路径（末尾不加/)
     * @param extNameArrayList 文件扩展名 为空或者*则搜索所有文件  为/则只搜索文件夹名
     * @param includeChildBoo 是否包含子路径
     * Exception 获得文件路径失败
     * 2006-4-1  11:26:00
     */
    protected void doSetFilePath (
            ArrayList<String> filePathArrl
            ,String basePathStr
            ,ArrayList<String> extNameArrayList
            ,boolean includeChildBoo) throws Exception {
        //校验扩展名序列
        final ArrayList<String> checkNameArrayList = new ArrayList<String>();
        if(extNameArrayList!=null) {
            //转换为小写
            for(Object element:extNameArrayList) {
                if(element==null) {
                    continue;
                }
                checkNameArrayList.add(((String)element).toLowerCase());
            }
        }
        basePathStr = swapString(basePathStr,"\\","/");
        //构建根路径对象
        File file = new File(basePathStr);
        //获得当前路径下，所有符合条件的文件和文件夹
        String[] filePathArrayListStrs = 
            file.list(new FilenameFilter() {
            //文件筛选类
            @Override
            public boolean accept(File dirPathFile, String lastNameStr) {
                lastNameStr = lastNameStr.toLowerCase(); //转换为小写
                //构建查询到的文件或路径
                File file = null;
                    try{
                        if (lastNameStr==null || "/".equals(lastNameStr)) {
                            file = new File(dirPathFile.getPath());
                        }else {
                            file = new File(dirPathFile.getPath()+"/"+lastNameStr);
                        }
                    }catch(Exception e) {
                        e.printStackTrace();
                    }
                if (file != null && file.isDirectory()) {
                    //判断是否为文件夹
                    return true;
                }else {
                    //扩展名分割点
                    int point = lastNameStr.lastIndexOf(".");
                    String extName = ""; //扩展名
                    if(point>-1) {
                        extName = lastNameStr.substring(point+1);
                    }
                    //是否为指定扩展名的文件
                    return checkNameArrayList.size() < 1
                            || checkNameArrayList.contains("*")
                            || checkNameArrayList.contains(extName);
                }
            }
        });
        //判断是否存在文件或文件夹
        if (filePathArrayListStrs != null && filePathArrayListStrs.length >0) {
            //循环筛选
            String bPath = basePathStr; //处理过的根路径
            if (bPath.endsWith("/")) {
                bPath = bPath.substring(0,bPath.length()-1);
            }
            for ( int i=0; i< filePathArrayListStrs.length; i++){
                //构建查询到的文件或路径
                String findFilePath = bPath+"/"+filePathArrayListStrs[i];
                //构造查询到的文件或文件夹
                File findFile = getFileByName(findFilePath,null);
                if (findFile.isDirectory() && includeChildBoo) {
                    //如果是文件夹，并且搜索子文件夹，则递归调用方法
                    doSetFilePath(filePathArrl,findFilePath,checkNameArrayList,includeChildBoo);
                    if (checkNameArrayList.size()<1) {
                        continue;
                    }
                    if (checkNameArrayList.contains("*") || checkNameArrayList.contains("/")) {
                        filePathArrl.add(findFilePath);
                    }
                }else {
                    //加入文件路径
                    filePathArrl.add(findFilePath);
                }
            }
        }
    }
    
    
    /**
     * 通过URL方式获得文件对象
     * @author 刘虻
     * @param filePathStr 文件相对路径
     * @return 对应的文件对象
     * @exception Exception 获取文件时发生异常
     * 2006-4-8  14:01:57
     */
    protected File getFileByName(
            String filePathStr,String basePath) throws Exception {
        //构造返回值
        File reFile = new File(getAllFilePath(filePathStr,basePath));
        if (!reFile.exists()) {
            throw new Exception("No Find The File By Path:["
                    +filePathStr+"] AllPath:["
                    +reFile.getPath()+"] BasePath:["+basePath+"]");
        }
        return reFile;
    }
    
    
    /**
     * 执行添加指定文件路径
     * @author 刘虻
     * 2009-8-21下午03:36:35
     * @param filePath 文件路径
     * @throws Exception 执行发生异常
     * @deprecated
     */
    public void addFilePath(String filePath) throws Exception {
        //构建路径文件对象
        File pathFile = new File(filePath);
        if(!pathFile.exists()) {
            return;
        }
        //暂时观察没必要加这句话
        //System.setProperty("java.library.path",System.getProperty("java.library.path")+File.pathSeparator+filePath);
        getAddUrlMethod()
            .invoke(
                    ClassLoader.getSystemClassLoader(), pathFile.toURL());
    }

    
    /**
     * 获取加载路径方法
     * @author 刘虻
     * 2009-8-21下午01:31:29
     * @return 加载路径方法
     * @throws Exception 执行发生异常
     */
    protected Method getAddUrlMethod() throws Exception {
        if(addUrlMethod==null) {
            addUrlMethod = 
                URLClassLoader.class
                    .getDeclaredMethod("addURL", URL.class);
            addUrlMethod.setAccessible(true);
        }
        return addUrlMethod;
    }
}
